package com.faiz.ugla.griplog;

import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Arrays;
import java.util.LinkedList;

import com.androidplot.xy.BarFormatter;
import com.androidplot.xy.BarRenderer;
import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.LineAndPointRenderer;
import com.androidplot.xy.SimpleXYSeries;
import com.androidplot.xy.XYPlot;
import com.ugla.shake.ShakeConstants;
import com.ugla.shake.ShakeDevice;
import com.ugla.shake.bluetooth.ShakeAndroidBluetoothGingerbread;

import android.os.Bundle;
import android.preference.PreferenceManager;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Color;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;


public class GripLog extends Activity implements OnClickListener, OnSharedPreferenceChangeListener {

	//Preference vars
	private SharedPreferences appSettings;
	private static String shakeAddress;

	//UI vars
	private Button shakeConn;
	private TextView participantID;

	//Debugging vars
	private static final String DEBUG_TAG = "GripLog";

	//ShakeDevice vars
	protected static ShakeDevice sk7;
	protected static boolean connected;

	//Plotting vars
	protected static final String PLOT_INTENT_ACTION = "PLOT";
	private IntentFilter plotIntentFilter;
	private XYPlot capPlot;
	private SimpleXYSeries capSensingSeries = null;
	private XYPlot accelPlot;
	private SimpleXYSeries xSeries = null;
	private SimpleXYSeries ySeries = null;
	private SimpleXYSeries zSeries = null;

	private LinkedList<Number> xHistory;
	private LinkedList<Number> yHistory;
	private LinkedList<Number> zHistory;

	private static final int HISTORY_SIZE = 30;

	{
		xHistory = new LinkedList<Number>();
		yHistory = new LinkedList<Number>();
		zHistory = new LinkedList<Number>();

		xSeries = new SimpleXYSeries("X");
		ySeries = new SimpleXYSeries("Y");
		zSeries = new SimpleXYSeries("Z");

	}

	private SampleReceiver sampleReceiver;

	//Participant and task vars
	private String currentParticipant;

	//LoggingService service intents
	private Intent loggingService;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_grip_log);

		PreferenceManager.setDefaultValues(this, R.xml.prefs, false);
		appSettings = PreferenceManager.getDefaultSharedPreferences(this);

		appSettings.registerOnSharedPreferenceChangeListener(this);
		shakeAddress = appSettings.getString("CONFIG_SHAKE_KEY", "");
		currentParticipant = appSettings.getString("CONFIG_PARTICIPANT_INFO_KEY", "1");

		//Create capacitive sensing series
		capSensingSeries = new SimpleXYSeries("");

		//Attach plot to view
		capPlot = (XYPlot) findViewById(R.id.capPlot);

		//Setup plot
		capPlot.addSeries(capSensingSeries, BarRenderer.class, new BarFormatter(Color.argb(100, 0, 200, 0), Color.rgb(0, 80, 0)));
		capPlot.setDomainStepValue(24);
		capPlot.setTicksPerRangeLabel(1);

		//Capacitive sensing range values from SHAKE
		capPlot.setRangeBoundaries(0, 255, BoundaryMode.FIXED);

		//Use custom domain value formatter
		capPlot.setDomainValueFormat(new CapIndexFormat());

		//Update our domain and range axis labels:
		capPlot.setDomainLabel("Sensor plate");
		capPlot.getDomainLabelWidget().pack();
		capPlot.setRangeLabel("Values");
		capPlot.getRangeLabelWidget().pack();

		capPlot.setGridPadding(10, 10, 10, 0);
		capPlot.disableAllMarkup();

		BarRenderer barRenderer = (BarRenderer) capPlot.getRenderer(BarRenderer.class);
		if(barRenderer != null) {
			// make our bars a little thicker than the default so they can be seen better:
			barRenderer.setBarWidth(7);
		}

		//Setup plotting area
		accelPlot = (XYPlot) findViewById(R.id.accelPlot);
		//accelPlot.setBorderStyle(BorderStyle.ROUNDED);
		accelPlot.setRangeBoundaries(-2000, 2000, BoundaryMode.FIXED);
		//accelPlot.setDomainBoundaries(0, 30, BoundaryMode.AUTO);
		accelPlot.addSeries(xSeries, LineAndPointRenderer.class, new LineAndPointFormatter(Color.rgb(100, 100, 200), Color.rgb(100, 100, 200), null));
		accelPlot.addSeries(ySeries, LineAndPointRenderer.class, new LineAndPointFormatter(Color.rgb(100, 200, 100), Color.rgb(100, 200, 100), null));
		accelPlot.addSeries(zSeries, LineAndPointRenderer.class, new LineAndPointFormatter(Color.rgb(200, 100, 100), Color.rgb(200, 100, 100), null));
		accelPlot.setDomainStepValue(5);
		accelPlot.setTicksPerRangeLabel(3);
		accelPlot.setDomainLabel("#Samples");
		accelPlot.getDomainLabelWidget().pack();
		accelPlot.setRangeLabel("milli-g (m/ms^2)");
		accelPlot.getRangeLabelWidget().pack();
		accelPlot.disableAllMarkup();
		//accelPlot.position(accelPlot.getLegendWidget(),20, XLayoutStyle.ABSOLUTE_FROM_RIGHT,35,YLayoutStyle.ABSOLUTE_FROM_BOTTOM,AnchorPosition.RIGHT_BOTTOM);

		//Assign labels to view
		participantID = (TextView) findViewById(R.id.participantID);

		//Set text to labels
		participantID.setText(currentParticipant);

		//Assign buttons to view
		shakeConn = (Button) findViewById(R.id.button_connect);

		//Disable all buttons
		shakeConn.setEnabled(false);

		//Attach listener to button
		shakeConn.setOnClickListener(this);

		//Initialise bluetooth connection
		connected = false;

		//Create FakeCallController intent
		loggingService = new Intent(this, LoggingService.class);

		//Keep the screen on
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

		//SampleReceiver vars
		sampleReceiver = new SampleReceiver();
		plotIntentFilter = new IntentFilter();
		plotIntentFilter.addAction(PLOT_INTENT_ACTION);
	}

	@Override
	public void onResume() {
		super.onResume();

		if(!shakeAddress.isEmpty() && !connected && Integer.parseInt(currentParticipant) > 0) {
			shakeConn.setEnabled(true);
		}

		if(connected) {
			shakeConn.setEnabled(true);
		} 

		if(shakeConn.isEnabled()) {
			//Do nothing
		}

		//Register sampleReceiver
		registerReceiver(sampleReceiver, plotIntentFilter);
	}

	@Override
	public void onPause() {
		super.onPause();

		//Unregister sampleReceiver
		if (sampleReceiver != null)
			unregisterReceiver(sampleReceiver);
	}

	@Override
	public void onStop() {
		super.onStop();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.activity_grip_log, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {

		switch(item.getItemId()) {
		case R.id.menu_settings :
			Intent settingsActivityIntent = new Intent(this, PrefsActivity.class);
			startActivity(settingsActivityIntent);
			break;
		case R.id.menu_about :
			Intent aboutActivityIntent = new Intent(this, About.class);
			startActivity(aboutActivityIntent);
			break;
		case R.id.menu_exit :

			//Stop loggingService - just in case
			if (isLoggingServiceRunning()) {
				stopService(loggingService);
			}

			finish();
			break;
		}

		return true;
	}

	@Override
	public void onClick(View v) {

		switch(v.getId()) {

		case R.id.button_connect:
			if (shakeConn.getText() == getString(R.string.button_conn)) {
				connectToShake();
			}
			else {
				disconnectShake(); 
			}
			break;
		}
	}

	private void connectToShake() {
		//Create SHAKE device
		sk7 = new ShakeDevice(ShakeConstants.ShakeDeviceType.SHAKE_SK7, new ShakeAndroidBluetoothGingerbread()); //Gingerbread version

		//Check if bluetooth adapter presents
		BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
		if (btAdapter != null) {

			//Check if the adapter is enabled
			if(!btAdapter.isEnabled()) {
				btAdapter.enable();
			}

			//Connect to SHAKE
			if(!connected) {
				//Connect to Shake
				connected = sk7.connect(shakeAddress);
				shakeConn.setText(getString(R.string.button_conn_dc));
				Log.d(DEBUG_TAG, "Connected!");

				//Configure shake
				configShake();

				//Start loggingService
				if (!isLoggingServiceRunning()) {
					startService(loggingService);
				}
			}

		} else {
			Toast.makeText(this, "It seems like that this device doesn't have a bluetooth adapter!", Toast.LENGTH_SHORT).show();
		}
	}

	private void disconnectShake() {
		if (connected) {
			//Reset SK7 to factory default
			//sk7.write(0x0113,0xBB);

			//Close connection
			sk7.close();

			//Reset flag
			connected = false;

			//Update UI (button)
			shakeConn.setText(getString(R.string.button_conn));

			//Stop loggingService
			if (isLoggingServiceRunning()) {
				stopService(loggingService);
			}

			Log.d(DEBUG_TAG, "Connection to SHAKE has been closed!");
		}
	}

	private void configShake() {

		//Reset SK7 to factory default
		sk7.write(0x0113,0xBB);

		//Switch on accelerometer, gyro and capacitive sensors
		sk7.write_power_state(0x07); //switch off gyro

		//Switch data output to binary and enable timestamp (although timestamp always return 0)
		//Register 0x0002 : 00001010 -> 0x0A
		//sk7.write(0x0002,0x0A);

		//ExtCS3 Concurrent sampling
		sk7.write(0x0050,0x01);

		//Accelerometer sample rate: 50Hz
		//sk7.write(0x0008,0X32);
		sk7.write_sample_rate(ShakeConstants.SHAKE_SENSOR_ACC, 50);

		//ExtCS3 data output format - disable compass heading
		//sk7.write(0x0001,0x00);

		//Gyro sample rate: 50Hz
		//sk7.write(0x0009,0X32);
		sk7.write_sample_rate(ShakeConstants.SHAKE_SENSOR_GYRO, 50);

		//Capacitive sensing (internal/external) sample rate: 50Hz
		//sk7.write(0x000C,0X32);
		sk7.write_sample_rate(ShakeConstants.SHAKE_SENSOR_SK7_CAP, 50);
	}

	//Bar plotting class
	private class CapIndexFormat extends Format {

		private static final long serialVersionUID = 1L;

		@Override
		public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
			Number num = (Number) obj;

			int roundNum = (int) (num.floatValue() + 0.5f);
			switch(roundNum) {
			case 0:
				toAppendTo.append("A");
				break;
			case 1:
				toAppendTo.append("B");
				break;
			case 2:
				toAppendTo.append("C");
				break;
			case 3:
				toAppendTo.append("D");
				break;
			case 4:
				toAppendTo.append("E");
				break;
			case 5:
				toAppendTo.append("F");
				break;
			case 6:
				toAppendTo.append("G");
				break;
			case 7:
				toAppendTo.append("H");
				break;
			case 8:
				toAppendTo.append("I");
				break;
			case 9:
				toAppendTo.append("J");
				break;
			case 10:
				toAppendTo.append("K");
				break;
			case 11:
				toAppendTo.append("L");
				break;
			case 12:
				toAppendTo.append("M");
				break;
			case 13:
				toAppendTo.append("N");
				break;
			case 14:
				toAppendTo.append("O");
				break;
			case 15:
				toAppendTo.append("P");
				break;
			case 16:
				toAppendTo.append("Q");
				break;
			case 17:
				toAppendTo.append("R");
				break;
			case 18:
				toAppendTo.append("S");
				break;
			case 19:
				toAppendTo.append("T");
				break;
			case 20:
				toAppendTo.append("U");
				break;
			case 21:
				toAppendTo.append("V");
				break;
			case 22:
				toAppendTo.append("W");
				break;
			case 23:
				toAppendTo.append("X");
				break;
			default:
				toAppendTo.append("Unknown");
			}
			return toAppendTo;
		}

		@Override
		public Object parseObject(String arg0, ParsePosition arg1) {
			// TODO Auto-generated method stub
			return null;
		}
	}

	@Override
	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
		//Set default participant ID to 1
		currentParticipant = sharedPreferences.getString("CONFIG_PARTICIPANT_INFO_KEY", "1");
		shakeAddress = sharedPreferences.getString("CONFIG_SHAKE_KEY", "");
		participantID.setText(currentParticipant);

		if (currentParticipant.equals(""))
			shakeConn.setEnabled(false);
		else {
			if(Integer.parseInt(currentParticipant) < 0 )
				shakeConn.setEnabled(false);
		}

		Log.d(DEBUG_TAG, "SHAKE address: " + shakeAddress);
		Log.d(DEBUG_TAG, "Participant#: " + currentParticipant);
	}

	//Check if LoggingService is running
	private boolean isLoggingServiceRunning() {
		ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
		for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
			if (LoggingService.class.getName().equals(service.service.getClassName())) {
				return true;
			}
		}
		return false;
	}

	private void updatePlot(Intent plotIntent) {
		int[] cap = plotIntent.getIntArrayExtra("Cap");
		int[] acc = plotIntent.getIntArrayExtra("Acc");

		Number[] capList = {cap[0], cap[1], cap[2], cap[3], cap[4], cap[5], cap[6], cap[7], cap[8], cap[9], cap[10], cap[11], cap[12], cap[13], cap[14], cap[15], cap[16], cap[17], cap[18], cap[19], cap[20], cap[21], cap[22], cap[23]};

		//Debugging
		//Log.d(DEBUG_TAG, "X: " + cap[0] + " W: " + cap[1] + " V: " + cap[2] + " U: " + cap[3] + " T: " + cap[4] + " S: " + cap[5] + " R: " + cap[6] + " Q: " + cap[7] + " P: " +  cap[8] + " O: " + cap[9] + " N: " + cap[10] + " M: " + cap[11] + " L: " + cap[12] + " K: " + cap[13] + " J: " + cap[14] + " I: " + cap[15] + " H: " + cap[16] + " G: " + cap[17] + " F: " + cap[18] + " E: " + cap[19] + " D: " + cap[20] + " C: " + cap[21] + " B: " + cap[22] + " A: " + cap[23]);

		capSensingSeries.setModel(Arrays.asList(capList), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);

		//get rid the oldest sample in history:
		if (xHistory.size() > HISTORY_SIZE) {
			xHistory.removeFirst();
			yHistory.removeFirst();
			zHistory.removeFirst();
		}

		//add the latest history sample:
		xHistory.addLast(acc[0]);
		yHistory.addLast(acc[1]);
		zHistory.addLast(acc[2]);

		xSeries.setModel(xHistory, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
		ySeries.setModel(yHistory, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
		zSeries.setModel(zHistory, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);

		capPlot.redraw();
		accelPlot.redraw();
	}

	private class SampleReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			updatePlot(intent);
		}
	}
}